// $Id: buffer.c 64 2011-04-23 11:35:44Z cedric.shih@gmail.com $
/*
 * Copyright (C) 2010 Cedric Shih <cedric.shih@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <unistd.h>
#include <errno.h>

#include "log.h"
#include "socket.h"
#include "buffers.h"

#include "buffer.h"

#define MIN(a,b) a > b ? b : a

#ifdef WIN32
#define EWOULDBLOCK EAGAIN
#endif

struct niobuffer *niobuffer_new(size_t capacity)
{
	struct niobuffer *buffer;

	niodebug("constructing: %d bytes", capacity);

	if (!(buffer = calloc(1, sizeof(*buffer)))) {
		nioerror("failed to calloc");
		goto error;
	}

	TAILQ_INIT(&buffer->wrapped);
	TAILQ_INIT(&buffer->cloned);

	if (!(buffer->data = malloc(capacity))) {
		nioerror("failed to malloc: %d bytes", capacity);
		goto error;
	}

	buffer->type = NIO_BUFFER_DIRECT;
	buffer->limit = buffer->capacity = capacity;
	return buffer;

error:
	niobuffer_free(buffer);
	return NULL;
}

void niobuffer_free(struct niobuffer *buffer)
{
	struct niobuffer_item *item;

	if (!buffer) {
		return;
	}

	niodebug("destructing: %p", buffer);

	if (!TAILQ_EMPTY(&buffer->cloned)) {
		niodebug("clearing cloned list");

		niobuffer_list_clear(&buffer->cloned);
	}

	switch (buffer->type) {
	case NIO_BUFFER_UNKNOWN:
		nioerror("unexpected type: %d", buffer->type);
		break;
	case NIO_BUFFER_WRAPPER:
		if (!TAILQ_EMPTY(&buffer->wrapped)) {
			niodebug("clearing wrapped list");

			while ((item = TAILQ_FIRST(&buffer->wrapped))) {
				TAILQ_REMOVE(&buffer->wrapped, item, entry);
//				niodebug("freeing wrapped clone");
//				niobuffer_free(item->buffer);
				free(item);
			}
		}
	case NIO_BUFFER_DIRECT:
		if (buffer->data) {
			free(buffer->data);
		}
		free(buffer);
		break;
	case NIO_BUFFER_ROTATED:
		if (!buffer->released) {
			niodebug("releasing rotated buffer from pool");
			niobuffers_release(buffer->source.factory, buffer);
		}
		break;
	case NIO_BUFFER_CLONED:
		free(buffer);
		break;
	}
}

struct niobuffer *niobuffer_clone(struct niobuffer *buffer)
{
	int rc;
	struct niobuffer *clone = NULL, *wrapped;
	struct niobuffer_item *item;

	niodebug("cloning %p: pos=%d, limit=%d, mark=%d", buffer,
			buffer->position, buffer->limit, buffer->mark);

	switch (buffer->type) {
	case NIO_BUFFER_UNKNOWN:
		nioerror("unexpected type: %d", buffer->type);
		goto error;
	case NIO_BUFFER_DIRECT:
	case NIO_BUFFER_ROTATED:
	case NIO_BUFFER_CLONED:
	case NIO_BUFFER_WRAPPER:
		break;
	}

	if (!(clone = calloc(1, sizeof(*clone)))) {
		nioerror("failed to calloc");
		goto error;
	}

	TAILQ_INIT(&clone->wrapped);
	TAILQ_INIT(&clone->cloned);

	if ((rc = niobuffer_list_add(&buffer->cloned, clone))) {
		nioerror("failed to add: %s", nio_strerror(rc));
		goto error;
	}

	clone->source.buffer = buffer;
	clone->type = NIO_BUFFER_CLONED;
	clone->data = buffer->data;
	clone->capacity = buffer->capacity;
	clone->limit = buffer->limit;
	clone->position = buffer->position;
	clone->mark = clone->mark;

	TAILQ_FOREACH(item, &buffer->wrapped, entry) {
		if (!(wrapped = niobuffer_clone(item->buffer))) {
			nioerror("failed to clone");
			goto error;
		}

		niobuffer_list_add(&clone->wrapped, wrapped);
	}

	clone->num_wrapped = buffer->num_wrapped;

	return clone;

error:
	niobuffer_free(clone);
	return NULL;
}

struct niobuffer *niobuffer_wrapper(void)
{
	struct niobuffer *buffer;

	niodebug("creating wrapper");

	if (!(buffer = calloc(1, sizeof(*buffer)))) {
		nioerror("failed to calloc");
		goto error;
	}

	TAILQ_INIT(&buffer->wrapped);
	TAILQ_INIT(&buffer->cloned);

	buffer->type = NIO_BUFFER_WRAPPER;
	return buffer;

error:
	niobuffer_free(buffer);
	return NULL;
}

int niobuffer_wrap(struct niobuffer *buffer, struct niobuffer *target)
{
	int rc;

	if (!buffer || !target) {
		nioerror("null argument(s)");
		rc = NIO_EINVAL;
		goto finally;
	}

	switch (buffer->type) {
	case NIO_BUFFER_UNKNOWN:
		nioerror("unexpected type: %d", buffer->type);
		rc = NIO_EUNKNOWN;
		goto finally;
	case NIO_BUFFER_DIRECT:
	case NIO_BUFFER_ROTATED:
	case NIO_BUFFER_CLONED:
		nioerror("not supported type: %d", buffer->type);
		rc = NIO_EPERM;
		break;
	case NIO_BUFFER_WRAPPER:
		break;
	}

	niodebug("wrapping %p: pos=%d, limit=%d, mark=%d", target,
			target->position, target->limit, target->mark);

	if ((rc = niobuffer_list_add(&buffer->wrapped, target))) {
		nioerror("failed to add: %s", nio_strerror(rc));
		goto finally;
	}

	buffer->capacity += target->capacity;
	buffer->limit += target->limit;
	buffer->num_wrapped++;

	if (!buffer->current) {
		buffer->current = TAILQ_FIRST(&buffer->wrapped);
	}

	rc = 0;

finally:
	return rc;
}

struct niobuffer *niobuffer_slice(struct niobuffer *buffer,
		size_t position, size_t capacity)
{
	struct niobuffer *clone = NULL;

	if (!buffer) {
		nioerror("null argument(s)");
		goto error;
	}

	niodebug("slicing %d bytes at %d from %d@%p", capacity, position,
			buffer->capacity, buffer->data);

	if (capacity > (niobuffer_capacity(buffer) - position)) {
		nioerror("underflows");
		goto error;
	}

	if (!(clone = niobuffer_clone(buffer))) {
		nioerror("failed to clone");
		goto error;
	}

	clone->data = buffer->data + position;
	clone->limit = clone->capacity = capacity;
	clone->position = 0;
	clone->mark = 0;

	return clone;

error:
	niobuffer_free(clone);
	return NULL;
}

int niobuffer_get(struct niobuffer *buffer, int *b)
{
	int rc;

	if (!buffer || !b) {
		nioerror("null argument(s)");
		rc = NIO_EINVAL;
		goto finally;
	}

	if ((buffer->limit - buffer->position) <= 0) {
		nioerror("underflows");
		rc = NIO_EUNDERFLOW;
		goto finally;
	}

	niotrace("getting at: %d", buffer->position);

	switch (buffer->type) {
	case NIO_BUFFER_UNKNOWN:
		nioerror("unexpected type: %d", buffer->type);
		rc = NIO_EUNKNOWN;
		goto finally;
	case NIO_BUFFER_DIRECT:
	case NIO_BUFFER_ROTATED:
	case NIO_BUFFER_CLONED:
		*b = *(buffer->data + buffer->position);
		break;
	case NIO_BUFFER_WRAPPER:
		if ((rc = niobuffer_get(buffer->current->buffer, b))) {
			nioerror("failed to get wrapped buffer: %s",
					nio_strerror(rc));
			goto finally;
		}

		if (niobuffer_remaining(buffer->current->buffer) <= 0) {
			buffer->current = TAILQ_NEXT(buffer->current, entry);
		}
		break;
	}

	buffer->position++;
	rc = 0;

finally:
	return rc;
}

int niobuffer_gets(struct niobuffer *buffer, unsigned char *data, size_t len)
{
	int rc;
	size_t pending, offset, remaining, read;

	if (!buffer || !data || !len) {
		nioerror("null or zero argument(s)");
		rc = NIO_EINVAL;
		goto finally;
	}

	if (len > (buffer->limit - buffer->position)) {
		nioerror("underflows");
		rc = NIO_EUNDERFLOW;
		goto finally;
	}

	niodebug("getting %d bytes at: %d", len, buffer->position);

	switch (buffer->type) {
	case NIO_BUFFER_UNKNOWN:
		nioerror("unexpected type: %d", buffer->type);
		rc = NIO_EUNKNOWN;
		goto finally;
	case NIO_BUFFER_DIRECT:
	case NIO_BUFFER_ROTATED:
	case NIO_BUFFER_CLONED:
		memcpy(data, buffer->data + buffer->position, len);
		break;
	case NIO_BUFFER_WRAPPER:
		offset = 0;
		pending = len;

		while (pending > 0) {
			remaining = niobuffer_remaining(
					buffer->current->buffer);

			if (pending > remaining) {
				read = remaining;
			} else {
				read = pending;
			}

			if ((rc = niobuffer_gets(buffer->current->buffer,
					data + offset, read))) {
				nioerror("failed to gets wrapped buffer: %s",
						nio_strerror(rc));
				goto finally;
			}

			if (niobuffer_remaining(buffer->current->buffer) <= 0) {
				buffer->current = TAILQ_NEXT(
						buffer->current, entry);
			}

			offset += read;
			pending -= read;
		}
		break;
	}

	buffer->position += len;
	rc = 0;

finally:
	return rc;
}

int niobuffer_skip(struct niobuffer *buffer, size_t offset)
{
	int rc;
	size_t pending, remaining, skip;

	if (!buffer) {
		nioerror("null or zero argument(s)");
		rc = NIO_EINVAL;
		goto finally;
	}

	if (offset > (buffer->limit - buffer->position)) {
		nioerror("underflows");
		rc = NIO_EUNDERFLOW;
		goto finally;
	}

	niodebug("skipping: %d", offset);

	switch (buffer->type) {
	case NIO_BUFFER_UNKNOWN:
		nioerror("unexpected type: %d", buffer->type);
		rc = NIO_EUNKNOWN;
		goto finally;
	case NIO_BUFFER_DIRECT:
	case NIO_BUFFER_ROTATED:
	case NIO_BUFFER_CLONED:
		break;
	case NIO_BUFFER_WRAPPER:
		pending = offset;

		while (pending > 0) {
			remaining = niobuffer_remaining(
					buffer->current->buffer);

			if (pending > remaining) {
				skip = remaining;
			} else {
				skip = pending;
			}

			if ((rc = niobuffer_skip(buffer->current->buffer, skip))) {
				nioerror("failed to gets wrapped buffer: %s",
						nio_strerror(rc));
				goto finally;
			}

			if (niobuffer_remaining(buffer->current->buffer) <= 0) {
				buffer->current = TAILQ_NEXT(
						buffer->current, entry);
			}

			pending -= skip;
		}
		break;
	}

	buffer->position += offset;
	rc = 0;

finally:
	return rc;
}

int niobuffer_put(struct niobuffer *buffer, int b)
{
	int rc;
	size_t remain;

	if (!buffer) {
		nioerror("null argument");
		rc = NIO_EINVAL;
		goto finally;
	}

	if ((remain = (buffer->limit - buffer->position)) <= 0) {
		nioerror("out of buffer: pos=%d, limit=%d, mark=%d",
			buffer->position, buffer->limit, buffer->mark);
		rc = NIO_EOVERFLOW;
		goto finally;
	}

	niodebug("putting at: %d", buffer->position);

	switch (buffer->type) {
	case NIO_BUFFER_UNKNOWN:
		nioerror("unexpected type: %d", buffer->type);
		rc = NIO_EUNKNOWN;
		goto finally;
	case NIO_BUFFER_DIRECT:
	case NIO_BUFFER_ROTATED:
	case NIO_BUFFER_CLONED:
		*(buffer->data + buffer->position) = b;
		break;
	case NIO_BUFFER_WRAPPER:
		if ((rc = niobuffer_put(buffer->current->buffer, b))) {
			nioerror("failed to put wrapped buffer: %s",
					nio_strerror(rc));
			goto finally;
		}

		if (niobuffer_remaining(buffer->current->buffer) <= 0) {
			buffer->current = TAILQ_NEXT(buffer->current, entry);
		}
		break;
	}

	buffer->position++;
	rc = 0;

finally:
	return rc;
}

int niobuffer_puts(struct niobuffer *buffer,
		const unsigned char *data, size_t len)
{
	int rc;
	size_t pending, offset, remaining, write;

	if (!buffer || !data || !len) {
		nioerror("null or zero argument(s)");
		rc = NIO_EINVAL;
		goto finally;
	}

	if ((remaining = (buffer->limit - buffer->position)) <= 0) {
		nioerror("out of buffer: pos=%d, limit=%d, mark=%d",
			buffer->position, buffer->limit, buffer->mark);
		rc = NIO_EOVERFLOW;
		goto finally;
	}

	if (len > remaining) {
		nioerror("overflows: %d > %d", len, remaining);
		rc = NIO_EOVERFLOW;
		goto finally;
	}

	niodebug("putting %d bytes at: %d", len, buffer->position);

	switch (buffer->type) {
	case NIO_BUFFER_UNKNOWN:
		nioerror("unexpected type: %d", buffer->type);
		rc = -1;
		goto finally;
	case NIO_BUFFER_DIRECT:
	case NIO_BUFFER_ROTATED:
	case NIO_BUFFER_CLONED:
		memcpy(buffer->data + buffer->position, data, len);
		break;
	case NIO_BUFFER_WRAPPER:
		offset = 0;
		pending = len;

		while (pending > 0) {
			remaining = niobuffer_remaining(
					buffer->current->buffer);

			if (pending > remaining) {
				write = remaining;
			} else {
				write = pending;
			}

			if ((rc = niobuffer_puts(buffer->current->buffer,
					data + offset, write))) {
				nioerror("failed to gets wrapped buffer: %s",
						nio_strerror(rc));
				goto finally;
			}

			if (niobuffer_remaining(buffer->current->buffer) <= 0) {
				buffer->current = TAILQ_NEXT(
						buffer->current, entry);
			}

			offset += write;
			pending -= write;
		}
		break;
	}

	buffer->position += len;
	rc = 0;

finally:
	return rc;
}

int niobuffer_putf(struct niobuffer *buffer, const char *fmt, ...)
{
	int rc, len;
	va_list ap;
	size_t remain;

	if (!buffer || !fmt) {
		nioerror("null argument(s)");
		rc = NIO_EINVAL;
		goto finally;
	}

	if ((remain = (buffer->limit - buffer->position)) <= 0) {
		nioerror("out of buffer: pos=%d, limit=%d, mark=%d",
			buffer->position, buffer->limit, buffer->mark);
		rc = NIO_EOVERFLOW;
		goto finally;
	}

	niodebug("putting formatted at: %d", buffer->position);

	switch (buffer->type) {
	case NIO_BUFFER_UNKNOWN:
		nioerror("unexpected type: %d", buffer->type);
		rc = -1;
		goto finally;
	case NIO_BUFFER_DIRECT:
	case NIO_BUFFER_ROTATED:
	case NIO_BUFFER_CLONED:
		break;
	case NIO_BUFFER_WRAPPER:
		nioerror("not supported");
		rc = NIO_EPERM;
		goto finally;
	}

	va_start(ap, fmt);
	len = vsnprintf((char *)(buffer->data + buffer->position),
			remain, fmt, ap);
	va_end(ap);

	if (len >= remain) {
		nioerror("overflows: %d >= %d", len, remain);
		rc = NIO_EOVERFLOW;
		goto finally;
	} else if (len < 0) {
		nioerror("failed to printf");
		rc = NIO_EUNKNOWN;
		goto finally;
	}

	buffer->position += len;
	rc = 0;

finally:
	return rc;
}

int niobuffer_read(struct niobuffer *buffer, struct niosocket *socket)
{
	int rc = 0;
	ssize_t len;
	size_t remaining, position;

	if (!buffer) {
		nioerror("null argument");
		rc = NIO_EINVAL;
		goto finally;
	}

	if ((remaining = (buffer->limit - buffer->position)) <= 0) {
		nioerror("out of buffer: pos=%d, limit=%d, mark=%d",
			buffer->position, buffer->limit, buffer->mark);
		rc = NIO_EOVERFLOW;
		goto finally;
	}

	niodebug("reading socket %d: pos=%d, limit=%d, mark=%d",
			socket->descriptor,
			buffer->position, buffer->limit, buffer->mark);

	switch (buffer->type) {
	case NIO_BUFFER_UNKNOWN:
		nioerror("unexpected type: %d", buffer->type);
		rc = -1;
		goto finally;
	case NIO_BUFFER_DIRECT:
	case NIO_BUFFER_ROTATED:
	case NIO_BUFFER_CLONED:
		if ((len = niosocket_read(socket,
				buffer->data + buffer->position,
				remaining)) < 0) {
			rc = errno ? errno : -1;

			if ((rc == EAGAIN || rc == EINPROGRESS)) {
				rc = NIO_EAGAIN;
			} else {
				nioerror("failed to read: %s", strerror(rc));
				rc = NIO_EUNKNOWN;
			}
			goto finally;
		}

		if (len == 0) {
			rc = NIO_ECONNRESET;
			goto finally;
		}

		buffer->position += len;

		niodebug("%d bytes read from socket %d: "
				"pos=%d, limit=%d, mark=%d",
				len, socket->descriptor, buffer->position,
				buffer->limit, buffer->mark);

		rc = 0;
		break;
	case NIO_BUFFER_WRAPPER:
		while (niobuffer_remaining(buffer) > 0) {
			position = niobuffer_position(
					buffer->current->buffer);

			if ((rc = niobuffer_read(buffer->current->buffer,
					socket))) {
				break;
			}

			buffer->position += niobuffer_position(
					buffer->current->buffer)
					- position;

			if (niobuffer_remaining(buffer->current->buffer) <= 0) {
				buffer->current = TAILQ_NEXT(
						buffer->current, entry);
			}
		}
		break;
	}

finally:
	return rc;
}

int niobuffer_write(struct niobuffer *buffer, struct niosocket *socket)
{
	int rc = 0;
	ssize_t len;
	size_t remaining, position;

	if (!buffer) {
		nioerror("null argument");
		rc = NIO_EINVAL;
		goto finally;
	}

	if ((remaining = (buffer->limit - buffer->position)) <= 0) {
		nioerror("out of buffer: pos=%d, limit=%d, mark=%d",
			buffer->position, buffer->limit, buffer->mark);
		rc = NIO_EUNDERFLOW;
		goto finally;
	}

	niodebug("writing to socket %d: pos=%d, limit=%d, mark=%d",
			socket->descriptor,
			buffer->position, buffer->limit, buffer->mark);

	switch (buffer->type) {
	case NIO_BUFFER_UNKNOWN:
		nioerror("unexpected type: %d", buffer->type);
		rc = -1;
		goto finally;
	case NIO_BUFFER_DIRECT:
	case NIO_BUFFER_ROTATED:
	case NIO_BUFFER_CLONED:
		if ((len = niosocket_write(socket,
				buffer->data + buffer->position,
				(buffer->limit - buffer->position))) < 0) {
			rc = errno ? errno : -1;

			if (rc != EAGAIN && rc != EWOULDBLOCK) {
				nioerror("failed to write: %s", strerror(rc));
				rc = NIO_EIO;
				goto finally;
			}
		} else {
			buffer->position += len;

			niodebug("%d bytes written to socket %d: "
					"pos=%d, limit=%d, mark=%d",
					len, socket->descriptor,
					buffer->position, buffer->limit,
					buffer->mark);
		}

		rc = 0;
		break;
	case NIO_BUFFER_WRAPPER:
		while (niobuffer_remaining(buffer) > 0) {
			position = niobuffer_position(
					buffer->current->buffer);

			if ((rc = niobuffer_write(buffer->current->buffer,
					socket))) {
				break;
			}

			buffer->position += niobuffer_position(
					buffer->current->buffer)
					- position;

			if (niobuffer_remaining(buffer->current->buffer) <= 0) {
				buffer->current = TAILQ_NEXT(
						buffer->current, entry);
			}
		}
		break;
	}

finally:
	return rc;
}

int niobuffer_load(struct niobuffer *buffer, struct niobuffer *source)
{
	int rc;
	size_t len, pending, remaining, gets;

	if (!buffer || !source) {
		nioerror("null argument(s)");
		rc = NIO_EINVAL;
		goto finally;
	}

	niodebug("loading from: pos=%d, limit=%d, mark=%d",
			source->position, source->limit, source->mark);
	niodebug("loading to: pos=%d, limit=%d, mark=%d",
			buffer->position, buffer->limit, buffer->mark);

	len = niobuffer_remaining(source);

	if (len > (buffer->limit - buffer->position)) {
		nioerror("overflows: %d > %d", len,
				buffer->limit - buffer->position);
		rc = NIO_EOVERFLOW;
		goto finally;
	}

	switch (buffer->type) {
	case NIO_BUFFER_UNKNOWN:
		nioerror("unexpected type: %d", buffer->type);
		rc = -1;
		goto finally;
	case NIO_BUFFER_DIRECT:
	case NIO_BUFFER_ROTATED:
	case NIO_BUFFER_CLONED:
		if ((rc = niobuffer_gets(source,
				buffer->data + buffer->position, len))) {
			nioerror("failed to gets: %s", nio_strerror(rc));
			goto finally;
		}
		buffer->position += len;
		break;
	case NIO_BUFFER_WRAPPER:
		pending = len;

		while (pending > 0) {
			remaining = niobuffer_remaining(
					buffer->current->buffer);

			if (pending > remaining) {
				gets = remaining;
			} else {
				gets = pending;
			}

			if ((rc = niobuffer_gets(source,
					buffer->current->buffer->data
					+ buffer->current->buffer->position,
					gets))) {
				nioerror("failed to gets: %s",
						nio_strerror(rc));
				goto finally;
			}

			if (niobuffer_remaining(buffer->current->buffer) <= 0) {
				buffer->current = TAILQ_NEXT(
						buffer->current, entry);
			}

			pending -= gets;
		}
		break;
	}

	rc = 0;

finally:
	return rc;
}

inline unsigned char *niobuffer_array(struct niobuffer *buffer)
{
	if (buffer->type == NIO_BUFFER_WRAPPER) {
		niowarn("attempt to get array of a buffer wrapper");
	}
	return buffer->data;
}

inline size_t niobuffer_capacity(struct niobuffer *buffer)
{
	return buffer->capacity;
}

inline size_t niobuffer_position(struct niobuffer *buffer)
{
	return buffer->position;
}

int niobuffer_set_position(struct niobuffer *buffer, size_t position)
{
	size_t pending;
	struct niobuffer_item *item;

	if (!buffer) {
		nioerror("null argument");
		return NIO_EINVAL;
	}

	if (position > buffer->limit || position > buffer->capacity) {
		nioerror("out of range");
		return NIO_ERANGE;
	}

	niodebug("setting position to %d: pos=%d, limit=%d, mark=%d",
			position, buffer->position, buffer->limit, buffer->mark);

	if (buffer->type == NIO_BUFFER_WRAPPER) {
		if (position == 0) {
			buffer->current = TAILQ_FIRST(&buffer->wrapped);
		}

		pending = position;

		TAILQ_FOREACH(item, &buffer->wrapped, entry) {
			if (pending <= 0) {
				niobuffer_set_position(item->buffer, 0);
				continue;
			}

			if (niobuffer_limit(item->buffer) < pending) {
				niobuffer_set_position(item->buffer,
						niobuffer_limit(item->buffer));
				pending -= niobuffer_limit(item->buffer);
			} else {
				buffer->current = item;
				niobuffer_set_position(item->buffer, pending);
				pending = 0;
			}
		}
	}

	buffer->position = position;
	return 0;
}

inline size_t niobuffer_limit(struct niobuffer *buffer)
{
	return buffer->limit;
}

int niobuffer_set_limit(struct niobuffer *buffer, size_t limit)
{
	size_t pending;
	struct niobuffer_item *item;

	if (!buffer) {
		nioerror("null argument");
		return NIO_EINVAL;
	}

	if (limit > buffer->capacity) {
		nioerror("out of range");
		return NIO_ERANGE;
	}

	niodebug("setting limit to %d: pos=%d, limit=%d, mark=%d",
			limit, buffer->position, buffer->limit, buffer->mark);

	if (buffer->type == NIO_BUFFER_WRAPPER) {
		pending = limit;

		TAILQ_FOREACH(item, &buffer->wrapped, entry) {
			if (pending <= 0) {
				niobuffer_set_limit(item->buffer, 0);
				continue;
			}

			if (niobuffer_capacity(item->buffer) < pending) {
				niobuffer_set_limit(item->buffer,
						niobuffer_capacity(
						item->buffer));
				pending -= niobuffer_capacity(item->buffer);
			} else {
				niobuffer_set_limit(item->buffer, pending);
				pending = 0;
			}
		}
	}

	if (limit < buffer->position) {
		buffer->position = limit;
	}

	buffer->limit = limit;
	return 0;
}

inline size_t niobuffer_remaining(struct niobuffer *buffer)
{
	return buffer->limit - buffer->position;
}

void niobuffer_clear(struct niobuffer *buffer)
{
	struct niobuffer_item *item;

	niodebug("clearing: pos=%d, limit=%d, mark=%d",
			buffer->position, buffer->limit, buffer->mark);

	if (buffer->type == NIO_BUFFER_WRAPPER) {
		TAILQ_FOREACH(item, &buffer->wrapped, entry) {
			niobuffer_clear(item->buffer);
		}
	}

	buffer->limit = buffer->capacity;
	buffer->position = 0;
	buffer->mark = 0;
}

void niobuffer_flip(struct niobuffer *buffer)
{
	niodebug("flipping: pos=%d, limit=%d, mark=%d",
			buffer->position, buffer->limit, buffer->mark);

	niobuffer_set_limit(buffer, buffer->position);
	niobuffer_set_position(buffer, 0);
	buffer->mark = 0;
}

void niobuffer_rewind(struct niobuffer *buffer)
{
	niodebug("rewinding: pos=%d, limit=%d, mark=%d",
			buffer->position, buffer->limit, buffer->mark);
	niobuffer_set_position(buffer, 0);
	buffer->mark = 0;
}

void niobuffer_mark(struct niobuffer *buffer)
{
	niodebug("marking: pos=%d, limit=%d, mark=%d",
			buffer->position, buffer->limit, buffer->mark);
	buffer->mark = buffer->position;
}

void niobuffer_reset(struct niobuffer *buffer)
{
	niodebug("resetting: pos=%d, limit=%d, mark=%d",
			buffer->position, buffer->limit, buffer->mark);
	niobuffer_set_position(buffer, buffer->mark);
}

int niobuffer_compact(struct niobuffer *buffer)
{
	int rc;

	if (buffer->position <= 0) {
		rc = 0;
		goto finally;
	}

	switch (buffer->type) {
	case NIO_BUFFER_UNKNOWN:
		nioerror("unexpected type: %d", buffer->type);
		rc = -1;
		goto finally;
	case NIO_BUFFER_DIRECT:
	case NIO_BUFFER_ROTATED:
	case NIO_BUFFER_CLONED:
		break;
	case NIO_BUFFER_WRAPPER:
		nioerror("not yet supported");
		rc = NIO_EPERM;
		goto finally;
	}

	size_t remaining = niobuffer_remaining(buffer);

	niodebug("compacting: pos=%d, limit=%d, mark=%d",
			buffer->position, buffer->limit, buffer->mark);

	memmove(buffer->data, buffer->data + buffer->position, remaining);
	buffer->position = remaining;
	buffer->limit = buffer->capacity;
	rc = 0;

finally:
	return rc;
}

int niobuffer_resize(struct niobuffer *buffer, size_t capacity)
{
	int rc;
	unsigned char *data;

	if (!buffer || !capacity) {
		nioerror("null or zero argument(s)");
		rc = NIO_EINVAL;
		goto finally;
	}

	if (capacity < buffer->limit) {
		nioerror("underflows");
		rc = NIO_EUNDERFLOW;
		goto finally;
	}

	niodebug("resizing: %d", capacity);

	switch (buffer->type) {
	case NIO_BUFFER_UNKNOWN:
		nioerror("unexpected type: %d", buffer->type);
		rc = -1;
		goto finally;
	case NIO_BUFFER_DIRECT:
		break;
	case NIO_BUFFER_ROTATED:
	case NIO_BUFFER_CLONED:
	case NIO_BUFFER_WRAPPER:
		nioerror("not supported");
		rc = NIO_EPERM;
		goto finally;
	}

	if (!realloc(buffer->data, capacity)) {
		if (!(data = malloc(capacity))) {
			nioerror("failed to realloc: %d bytes", capacity);
			rc = NIO_ENOMEM;
			goto finally;
		}

		memcpy(data, buffer->data, MIN(capacity, buffer->capacity));
		free(buffer->data);
		buffer->data = data;
	}

	buffer->limit = MIN(buffer->limit, capacity);
	buffer->capacity = capacity;
	rc = 0;

finally:
	return rc;
}

int niobuffer_list_add(struct niobuffer_list *list, struct niobuffer *buffer)
{
	int rc;
	struct niobuffer_item *item;

	if (!(item = calloc(1, sizeof(*item)))) {
		nioerror("failed to calloc");
		rc = NIO_ENOMEM;
		goto finally;
	}

	item->buffer = buffer;
	TAILQ_INSERT_TAIL(list, item, entry);
	rc = 0;

finally:
	return rc;
}

void niobuffer_list_clear(struct niobuffer_list *list)
{
	struct niobuffer_item *item;

	while ((item = TAILQ_FIRST(list))) {
		TAILQ_REMOVE(list, item, entry);
		free(item);
	}
}
